package it.allerj.common.utility;

import it.allerj.lookup.AbstractLookup;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;

/**
 * 
 * @author Alessandro Veracchi
 * 
 */
public class HashMapUtil {

	public static void fillObjectFromHashMap(Object destinationObj, Map<String, Object> parameters) {
		// try to fills the object
		Set<String> tempParametersKey = new HashSet<String>(parameters.keySet());
		for (String key : tempParametersKey) {
			if (parameters.get(key) == null) {
				parameters.remove(key);
				continue;
			}
			if (parameters.get(key) instanceof String && "".equals(parameters.get(key)))
				parameters.put(key, null);
			if (parameters.get(key) instanceof Timestamp) {
				parameters.put(key, new Date(((Timestamp) parameters.get(key)).getTime()));
			}
		}
		try {
			BeanUtils.populate(destinationObj, parameters);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	public static void fillHashMapFromObject(Object sourceObj, Map<String, Object> destinationMap) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		fillHashMapFromObject(sourceObj, destinationMap, false, true, sourceObj.getClass());
	}

	public static void fillHashMapFromObject(Object sourceObj, Map<String, Object> destinationMap, boolean byAttributes, boolean convert) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		fillHashMapFromObject(sourceObj, destinationMap, byAttributes, convert, sourceObj.getClass());
	}

	public static List<Method> getDeclaredMethods(Object sourceObj) {
		return new ArrayList<Method>(Arrays.asList(sourceObj.getClass().getDeclaredMethods()));
	}

	public static List<Field> getDeclaredFields(Object sourceObj) {
		return new ArrayList<Field>(Arrays.asList(sourceObj.getClass().getDeclaredFields()));
	}

	public static List<Method> getDeclaredMethods(Class<?> sourceObjClass) {
		return new ArrayList<Method>(Arrays.asList(sourceObjClass.getDeclaredMethods()));
	}

	public static List<Field> getDeclaredFields(Class<?> sourceObjClass) {
		return new ArrayList<Field>(Arrays.asList(sourceObjClass.getDeclaredFields()));
	}

	public static List<Field> getFieldsFromObject(Object sourceObj, Class<?> untilAncestorClass) {
		List<Field> fields = getDeclaredFields(sourceObj);
		Class<?> subclass = sourceObj.getClass();
		Class<?> superClass = untilAncestorClass;
		while (subclass != null && !subclass.getName().equals(superClass.getName())) {
			subclass = subclass.getSuperclass();
			List<Field> innerFields = getDeclaredFields(subclass);
			for (Field field : innerFields) {
				fields.add(field);
			}
		}
		return fields;
	}

	public static List<Method> getMethodsFromObject(Object sourceObj, Class<?> untilAncestorClass) {
		List<Method> methods = getDeclaredMethods(sourceObj);
		Class<?> subclass = sourceObj.getClass();
		Class<?> superClass = untilAncestorClass;
		while (subclass != null && !subclass.getName().equals(superClass.getName())) {
			subclass = subclass.getSuperclass();
			List<Method> innerMethods = getDeclaredMethods(subclass);
			for (Method method : innerMethods) {
				methods.add(method);
			}
		}
		return methods;
	}

	public static void fillHashMapFromObject(Object sourceObj, Map<String, Object> destinationMap, boolean byAttributes, boolean convert, Class<?> untilAncestorClass) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		if (byAttributes) {
			List<Field> fields = getFieldsFromObject(sourceObj, untilAncestorClass);
			for (Field field : fields) {
				field.setAccessible(true);
				String key = field.getName();
				Object value = field.get(sourceObj);
				addToMap(destinationMap, key, value, convert);
			}
		}

		if (!byAttributes) {
			List<Method> methods = getMethodsFromObject(sourceObj, untilAncestorClass);
			for (Method method : methods)
				if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) {
					int mods = method.getModifiers();
					if (Modifier.isPrivate(mods))
						continue;
					String key = method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4);
					Object value = method.invoke(sourceObj, new Object[] {});
					addToMap(destinationMap, key, value, convert);
				}
		}

	}

	/**
	 * 
	 * @param destinationMap
	 * @param key
	 * @param value
	 * @param convert
	 */
	private static void addToMap(Map<String, Object> destinationMap, String key, Object value, boolean convert) {
		if (convert) {
			value = ConvertUtils.convert(value);
		}
		destinationMap.put(key, value);
	}

	public static boolean hasDefaultConstructor(Class<? extends Object> type) {
		boolean defaultCostructor = false;
		for (Constructor<?> costructor : type.getConstructors()) {
			if (costructor.getParameterTypes().length == 0) {
				defaultCostructor = true;
				break;
			}
		}
		return defaultCostructor;
	}

	public static boolean isInstanceOfPersistenceObject(Class<?> type) {
		boolean instanceOfPersistenceObject = false;
		if (!HashMapUtil.isAbstract(type) && !type.isInterface() && HashMapUtil.hasDefaultConstructor(type))
			try {
				Object obj = type.newInstance();
				if (obj instanceof it.allerj.common.persistence.PersistenceObject && !(obj instanceof AbstractLookup))
					instanceOfPersistenceObject = true;
			} catch (Exception e) {
				e.printStackTrace();
			}
		return instanceOfPersistenceObject;
	}

	public static boolean isAbstract(Class<?> type) {
		int mods = type.getModifiers();
		return Modifier.isAbstract(mods);
	}

}
